package edu.wpi.cs509.ui;

import java.awt.*;

import edu.wpi.cs509.representation.StrategoPiece;

/**
 * This layout manager enforces the layout rules for the graveyard 
 * pane. The layout of the graveyard contains the images of the 

 * stratego pieces and below each image is the number of pieces 
 * currently in the graveyard. The combination of the image and the
 * count is called a graveyard 'plot'.  
 * During initial placement, the count is the number of pieces 
 * remaining to be placed.
 * During game play, the count is the number of pieces removed from
 * the game.
 * 
 *    -----------------------------
 *    |        PlayerName         |
 *    +---------------------------+
 *    |                           |
 *    |  +---+ +---+ +---+ +---+  |
 *    |  | 1 | | 2 | | 3 | | 4 |  |
 *    |  +---+ +---+ +---+ +---+  |
 *    |    1     0     1     2    |
 *    |                           |
 *    |  +---+ +---+ +---+ +---+  |
 *    |  | 5 | | 6 | | 7 | | 8 |  |
 *    |  +---+ +---+ +---+ +---+  |
 *    |    2     1     4     4    |
 *    |                           |
 *    |  +---+ +---+ +---+ +---+  |
 *    |  | 9 | | S | | B | | F |  |
 *    |  +---+ +---+ +---+ +---+  |
 *    |    5     1     2     0    |
 *    |                           |
 *    +---------------------------+ 
 *  
 * @author afoltan
 *
 */
public class GraveyardLayout implements LayoutManager {

	// the layout of the graveyard as defined by rows and columns
	// NOTE: there must be at least 12 graveyard plot locations
	//   4x3, 2x6, 1x12, etc
	private static final int ROWS = 3;
	private static final int COLS = 4;
	
	// the number of pixels providing space between the graveyard plots
	private static final int GAP = 5;
	
	// a flag to indicate one-time initialization is complete
    private boolean initialized = false;
    
    // indicates this graveyard will be the opponents and not
    // show any piece values.  Only the single HIDDEN piece
    // will be present
    private boolean opaque = false;
    private StrategoPiece referencePiece = StrategoPiece.MARSHAL;
    
    // one-time initialization variables
    private int    minWidth                 = 0; 
    private int    minHeight                = 0;
    private int    preferredWidth           = 0;
    private int    preferredHeight          = 0;
    private double preferredAspectRatio     = 0.5; 
 
    public GraveyardLayout() {
    }
 
    
    /**
     * Constructor for the graveyard layout
     * 
     * @param opaque
     */
    public GraveyardLayout( boolean opaque ) {
    	this.opaque = opaque;
    	if (opaque == true) {
    		referencePiece = StrategoPiece.HIDDEN;
    	}
    }
    
    
    /* Required by LayoutManager. */
    public void addLayoutComponent(String name, Component comp) {
    }
 
    /* Required by LayoutManager. */
    public void removeLayoutComponent(Component comp) {
    }
 
    /**
     * Calculates the preferred width of the graveyard.
     * This is derived from the preferred image width of a stratego graphic image, and 
     * the number of COLS in the layout.
     *
     * @param graveyard - the graveyard to calculate the preferred width of
     * @return - the preferred width in pixels
     */
    private int calcPreferredWidth( Graveyard graveyard ) {
    	int width = 0;
    	
    	// include the width of each column
    	for (int i=0; i<COLS; i++) {
    		width += graveyard.getGraveyardPlot( referencePiece ).getPreferredSize().width;
    		width += GAP;
    	}
		
    	width -= GAP;	// no GAP after last column
    	
    	return width;
    }
    
    /**
     * Calculates the preferred height of the graveyard.
     * This is derived from the preferred image height of a stratego graphic image, and 
     * the number of ROWS in the layout.
     *
     * @param graveyard - the graveyard to calculate the preferred height of
     * @return - the preferred height in pixels
     */
    private int calcPreferredHeight( Graveyard graveyard ) {
    	int height = 0;

    	// include the player name header height
    	height += graveyard.getPlayerName().getPreferredSize().height;
    	
    	// include the height of each row
    	for (int i=0; i<ROWS; i++) {
    		height += GAP;
    		height += graveyard.getGraveyardPlot( referencePiece ).getPreferredSize().height;
    	}

    	return height;
    }
    
    /**
     * Sets the preferred and minimum widths and heights for the graveyard
     * @param graveyard - the graveyard on which the sizes are set
     */
    private void setSizes( Graveyard graveyard ) {
   		
    	// set the preferred size
    	preferredWidth  = calcPreferredWidth(graveyard);
    	preferredHeight = calcPreferredHeight(graveyard);

    	preferredAspectRatio = (double)preferredWidth / (double)preferredHeight;
    	
    	graveyard.setPreferredSize( new Dimension(preferredWidth, preferredHeight) );
    		
    	// set the minimum size (currently 1/2 the preferred size)
    	minWidth  = preferredWidth / 2;
    	minHeight = preferredHeight / 2;

    	graveyard.setMinimumSize( new Dimension(minWidth, minHeight) );
    		
    	// mark one-time initialization complete
    	initialized = true;
    	
    }
 
 
	/** Required by LayoutManager.
	 *
	 * Called by the Container class's getPreferredSize method, which is itself
	 * called under a variety of circumstances. This method should calculate and
	 * return the ideal size of the container, assuming that the components it
	 * contains will be at or above their preferred sizes. This method must take
	 * into account the container's internal borders, which are returned by the
	 * getInsets method.
	 */
	public Dimension preferredLayoutSize(Container parent) {
        Dimension dim = new Dimension(0, 0);
 
        // we only care about layouts of graveyards, so make sure the parent
        // is a Graveyard object.  If not, do no harm.
        if (parent instanceof Graveyard) {
        	Graveyard graveyard = (Graveyard)parent;
        	
        	if (!initialized) setSizes(graveyard);
 
        	//Always add the container's insets!
        	Insets insets = parent.getInsets();
        	dim.width = preferredWidth
                    + insets.left + insets.right;
        	dim.height = preferredHeight
                     + insets.top + insets.bottom;
        }
        
        return dim;
    }
 
	/** Required by LayoutManager.
	 * 
	 * Called by the Container getMinimumSize method, which is itself called
	 * under a variety of circumstances. This method should calculate and return
	 * the minimum size of the container, assuming that the components it
	 * contains will be at or above their minimum sizes. This method must take
	 * into account the container's internal borders, which are returned by the
	 * getInsets method.
	 */
    public Dimension minimumLayoutSize(Container parent) {
    	
    	Dimension dim = new Dimension(0, 0);
    	 
        // we only care about layouts of graveyards, so make sure the parent
        // is a Graveyard object.  If not, do no harm.
        if (parent instanceof Graveyard) {
        	Graveyard graveyard = (Graveyard)parent;
        	
        	if (!initialized) setSizes(graveyard);

        	//Always add the container's insets!
        	Insets insets = parent.getInsets();
        	dim.width = minWidth + insets.left + insets.right;
        	dim.height = minHeight + insets.top + insets.bottom;
        }
        
        return dim;
    }

    
	/** Required by LayoutManager.
	 * 
	 * Called to position and size each of the components in the container. A
	 * layout manager's layoutContainer method does not actually draw
	 * components. It simply invokes one or more of each component's setSize,
	 * setLocation, and setBounds methods to set the component's size and
	 * position. This method must take into account the container's internal
	 * borders, which are returned by the getInsets method. If appropriate, it
	 * should also take the container's orientation (returned by the
	 * getComponentOrientation method) into account. You cannot assume that the
	 * preferredLayoutSize or minimumLayoutSize methods will be called before
	 * layoutContainer is called.
	 * 
	 * @param parent - the parent container to which this layout is applied
	 */
    public void layoutContainer(Container parent) {
    	    	
        // we only care about layouts of graveyards, so make sure the parent
        // is a Graveyard object.  If not, do no harm.
    	 if (parent instanceof Graveyard) {
         	Graveyard graveyard = (Graveyard)parent;
        	
            if (!initialized) setSizes(graveyard);

        	Insets insets = parent.getInsets();
        	
        	// ignore borders
            int contentWidth = parent.getWidth() - (insets.left + insets.right);
            int contentHeight = parent.getHeight() - (insets.top + insets.bottom);

        	// resize the parent content area to maintain the graveyard aspect ratio
            // stretch the axis
            if ((double)contentWidth/(double)contentHeight > preferredAspectRatio) {
            	contentWidth = (int)((double)contentHeight * preferredAspectRatio);
            } else {
            	contentHeight = (int)((double)contentWidth / preferredAspectRatio);
            }
            
            
            // set the parent container to force the correct aspect ratio
            // include the borders
            parent.setSize( (contentWidth + insets.left + insets.right), 
            		        (contentHeight + insets.top + insets.bottom) );
            
            /*
             * Calculate the area of the player name
             */
            int x,y,w,h;
            
            x = insets.left;
            y = insets.top;
            w = contentWidth;
            h = graveyard.getPlayerName().getPreferredSize().height;
                        
            graveyard.getPlayerName().setBounds(x, y, w, h);

            /*
             * Calculate the area of a single graveyard plot (piece image plus text count)
             */
            
            int xOffset = insets.left;
            int yOffset = insets.top + h + GAP;
            		
            w = calcGraveyardPlotWidth( graveyard, contentWidth );
            h = calcGraveyardPlotHeight( graveyard, contentHeight );

            
            if (!opaque) {
	            
            	//Locate each specific graveyard plot
            	int col = 0, row = 0;
	            		
	            x = calcGraveyardPlotX( col, w, xOffset );
	            y = calcGraveyardPlotY( row, h, yOffset );
	
	            for (StrategoPiece p : StrategoPiece.ALL_GAME_PIECES) {
	            	
	            	graveyard.getGraveyardPlot( p ).setBounds(x, y, w, h);
	            	
	            	col++;
	            	
	            	if (col >= COLS) {
	            		col = 0;
	            		row++;
	                    y = calcGraveyardPlotY( row, h, yOffset );
	            	}
	
	                x = calcGraveyardPlotX( col, w, xOffset );
	            }
	            
            } else {
            	// opaque graveyard... setup only the HIDDEN piece
            	
            	//Center the piece

            	x = (contentWidth - w) / 2;
	            y = (contentHeight - h) / 2;
	            
	            graveyard.getGraveyardPlot( StrategoPiece.HIDDEN ).setBounds(x, y, w, h);
	            	
            }
            
            
    	 }
    	 
    }
    
    /**
     * Helper method to calculate the x-axis pixel location of a graveyard plot
     * based on its COL
     * 
     * @param col - the column of the graveyard plot
     * @param w - the width of a plot
     * @param offset - the x-axis offset (border)
     * @return
     */
    private int calcGraveyardPlotX( int col, int w, int offset ) {
    	int x;
    	
    	x = offset;
    	x += (col * (w+GAP));	// account for graveyard plot width
    	                		// account for space between columns
    	return x;
    }
    
    /**
     * Helper method to calculate the y-axis pixel location of a graveyard plot
     * based on its ROW
     * 
     * @param row - the row of the graveyard plot
     * @param w - the height of a plot
     * @param offset - the y-axis offset (border + player name + gap)
     * @return
     */
    private int calcGraveyardPlotY( int row, int h, int offset ) {
    	int y;    	

    	y = offset;
    	y += (row * (h+GAP));	// account for graveyard plot width
    	                		// account for space between column
    	return y;
    }
    
    /**
     * Helper method to calculate the width of a graveyard plot based
     * on the available content width, and the number of columns in the
     * graveyard.
     * 
     * @param contentWidth - the available width of the content area 
     * @return
     */
    private int calcGraveyardPlotWidth( Graveyard graveyard, int contentWidth ) {
    	int w;
    	w = contentWidth;		// start from the full content width
    	w -= (COLS-1)*GAP;		// remove the space between all columns
    	w = w / COLS;			// divide remaining equally among columns
    	return w;
	}

    /**
     * Helper method to calculate the height of a graveyard plot based
     * on the available content height, and the number of rows in the
     * graveyard.
     * 
     * @param contentHeight - the available height of the content area 
     * @return
     */
    private int calcGraveyardPlotHeight( Graveyard graveyard, int contentHeight) {
    	int h;
    	h = contentHeight;		// start from the full content height
    	h -= graveyard.getPlayerName().getPreferredSize().height;		// subtract out the player name area
    	h -= GAP;				// remove the space between player name and plots
    	h -= (ROWS-1)*GAP;		// remove the space between all rows
    	h = h / ROWS;			// divide remaining equally among rows
    	return h;
	}

}
